package ca.psquared.superbrixgame;

import com.google.common.collect.Lists;
import org.newdawn.slick.Color;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;

import java.util.List;

public class PieceProvider implements IPieceProvider {

    private enum PieceType {
        I, J, L, O, S, T, Z
    }

    private static PieceType getRandomPieceType() {
        final PieceType[] values = PieceType.values();
        final int choice = (int) (Math.random() * values.length);
        return values[choice];
    }

    @Override
    public IPiece getPiece() {

        final PieceType type = getRandomPieceType();

        switch (type) {
            case I:
                return new PieceI(ImageFactory.getImage(Color.orange));

            case J:
                return StandardPiece.newPieceJ(ImageFactory.getImage(Color.green));

            case L:
                return StandardPiece.newPieceL(ImageFactory.getImage(Color.yellow));

            case O:
                return new PieceO(ImageFactory.getImage(Color.blue));

            case S:
                return StandardPiece.newPieceS(ImageFactory.getImage(Color.red));

            case T:
                return StandardPiece.newPieceT(ImageFactory.getImage(Color.pink));

            case Z:
                return StandardPiece.newPieceZ(ImageFactory.getImage(Color.magenta));

            default:
                throw new IllegalArgumentException(String.format("Invalid PieceType: %s", type));
        }
    }

    private static class StandardPiece extends AbstractPiece {

        private final IBlock centreBlock;

        private enum RotateType {
            CW, CCW
        }

        public static StandardPiece newPieceS(Image image) {
            final List<IBlock> blocks = Lists.<IBlock>newArrayList(
                    new Block(image, 1, 0),
                    new Block(image, 2, 0),
                    new Block(image, 0, 1),
                    new Block(image, 1, 1)
            );
            final IBlock centreBlock = blocks.get(3);

            return new StandardPiece(blocks, centreBlock);
        }

        public static StandardPiece newPieceZ(Image image) {
            final List<IBlock> blocks = Lists.<IBlock>newArrayList(
                    new Block(image, 0, 0),
                    new Block(image, 1, 0),
                    new Block(image, 1, 1),
                    new Block(image, 2, 1)
            );
            final IBlock centreBlock = blocks.get(2);

            return new StandardPiece(blocks, centreBlock);
        }

        public static StandardPiece newPieceT(Image image) {
            final List<IBlock> blocks = Lists.<IBlock>newArrayList(
                    new Block(image, 1, 0),
                    new Block(image, 0, 1),
                    new Block(image, 1, 1),
                    new Block(image, 2, 1)
            );
            final IBlock centreBlock = blocks.get(2);

            return new StandardPiece(blocks, centreBlock);
        }

        public static StandardPiece newPieceJ(Image image) {
            final List<IBlock> blocks = Lists.<IBlock>newArrayList(
                    new Block(image, 1, 0),
                    new Block(image, 1, 1),
                    new Block(image, 1, 2),
                    new Block(image, 0, 2)
            );
            final IBlock centreBlock = blocks.get(1);

            return new StandardPiece(blocks, centreBlock);
        }

        public static StandardPiece newPieceL(Image image) {
            final List<IBlock> blocks = Lists.<IBlock>newArrayList(
                    new Block(image, 0, 0),
                    new Block(image, 0, 1),
                    new Block(image, 0, 2),
                    new Block(image, 1, 2)
            );
            final IBlock centreBlock = blocks.get(1);

            return new StandardPiece(blocks, centreBlock);
        }

        private StandardPiece(List<IBlock> blocks, IBlock centreBlock) {
            super(blocks);
            this.centreBlock = centreBlock;
        }

        @Override
        public void rotateCCw() {
            rotate(PieceProvider.StandardPiece.RotateType.CCW);
        }

        @Override
        public void rotateCw() {
            rotate(PieceProvider.StandardPiece.RotateType.CW);
        }

        private void rotate(RotateType type) {

            int[] matrix;
            int x = centreBlock.getX();
            int y = centreBlock.getY();

            if (type == PieceProvider.StandardPiece.RotateType.CW) {
                matrix = new int[]{
                        0, -1, x + y,
                        1, 0, y - x,
                        0, 0, 1
                };
            } else if (type == PieceProvider.StandardPiece.RotateType.CCW) {
                matrix = new int[]{
                        0, 1, x - y,
                        -1, 0, y + x,
                        0, 0, 1
                };
            } else {
                throw new IllegalArgumentException(String.format("Invalid RotateType: %s", type));
            }

            final List<Position> newPositions = Lists.newArrayList();
            final List<IBlock> blocks = getBlocks();

            boolean canRotate = true;
            for (IBlock block : blocks) {

                final Position oldPosition = Position.at(block.getX(), block.getY());
                final Position newPosition = transform(matrix, oldPosition);

                if (!getWorld().doesCollide(newPosition.x, newPosition.y)) {
                    newPositions.add(newPosition);
                } else {
                    canRotate = false;
                    break;
                }
            }

            if (canRotate) {
                for (int i = 0; i < blocks.size(); i++) {

                    final IBlock block = blocks.get(i);
                    final Position newPosition = newPositions.get(i);

                    block.setX(newPosition.x);
                    block.setY(newPosition.y);
                }
            }
        }
    }

    private static class PieceO extends AbstractPiece {

        private PieceO(Image image) {
            super(
                    Lists.<IBlock>newArrayList(
                            new Block(image, 0, 0),
                            new Block(image, 0, 1),
                            new Block(image, 1, 0),
                            new Block(image, 1, 1)
                    ));
        }

        @Override
        public void rotateCCw() {
        }

        @Override
        public void rotateCw() {
        }
    }

    private static class PieceI extends AbstractPiece {

        private int rotationToggle = 1;

        private PieceI(Image image) {
            super(
                    Lists.<IBlock>newArrayList(
                            new Block(image, 0, 0),
                            new Block(image, 1, 0),
                            new Block(image, 2, 0),
                            new Block(image, 3, 0))
            );
        }

        @Override
        public void rotateCw() {
            boolean blockCollided = false;
            final int localToggle = -this.rotationToggle;

            final List<IBlock> blocks = getBlocks();
            for (IBlock block : blocks) {
                int i = blocks.indexOf(block);
                if (getWorld().doesCollide(block.getX() + ((i - 1) * localToggle), block.getY() + ((i - 1) * localToggle))) {
                    blockCollided = true;
                }
            }
            if (!blockCollided) {
                for (IBlock block : blocks) {
                    int i = blocks.indexOf(block);
                    block.setX(block.getX() + ((i - 1) * localToggle));
                    block.setY(block.getY() + ((i - 1) * localToggle));
                }
                this.rotationToggle = localToggle;
            }
        }

        @Override
        public void rotateCCw() {
            rotateCw();
        }
    }


    private static abstract class AbstractPiece implements IPiece {

        private final List<IBlock> blocks;
        private IWorld world;

        protected AbstractPiece(List<IBlock> blocks) {
            this.blocks = blocks;
        }

        @Override
        public void setWorld(IWorld world) {
            this.world = world;
        }

        public IWorld getWorld() {
            return world;
        }

        @Override
        public List<IBlock> getBlocks() {
            return blocks;
        }

        @Override
        public void moveDown() {
            boolean blockCollided = false;

            for (IBlock block : blocks) {
                if (world.doesCollide(block.getX(), block.getY() + 1)) {
                    blockCollided = true;
                }
            }
            if (!blockCollided) {
                for (IBlock block : blocks) {
                    block.setY(block.getY() + 1);
                }
            } else {
                world.pieceHasLanded(this);
            }
        }

        @Override
        public void moveLeft() {
            boolean blockCollided = false;

            for (IBlock block : blocks) {
                if (world.doesCollide(block.getX() - 1, block.getY())) {
                    blockCollided = true;
                }
            }
            if (!blockCollided) {
                for (IBlock block : blocks) {
                    block.setX(block.getX() - 1);
                }
            }
        }

        @Override
        public void moveRight() {
            boolean blockCollided = false;

            for (IBlock block : blocks) {
                if (world.doesCollide(block.getX() + 1, block.getY())) {
                    blockCollided = true;
                }
            }
            if (!blockCollided) {
                for (IBlock block : blocks) {
                    block.setX(block.getX() + 1);
                }
            }
        }

        @Override
        public void render(Graphics graphics) {
            for (IBlock block : blocks) {
                block.render(graphics);
            }
        }

        @Override
        public int getWidth() {
            int minX = Integer.MAX_VALUE;
            int maxX = Integer.MIN_VALUE;

            for (IBlock block : this.getBlocks()) {
                minX = Math.min(minX, block.getX());
                maxX = Math.max(maxX, block.getX());
            }
            return (maxX - minX) + 1;
        }

        @Override
        public int getHeight() {
            int minY = Integer.MAX_VALUE;
            int maxY = Integer.MIN_VALUE;

            for (IBlock block : this.getBlocks()) {
                minY = Math.min(minY, block.getY());
                maxY = Math.max(maxY, block.getY());
            }
            return (maxY - minY) + 1;
        }
    }

    private static class Position {

        private final int x;
        private final int y;

        public static Position at(int x, int y) {
            return new Position(x, y);
        }

        private Position(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }

    private static Position transform(int[] matrix, Position originalPosition) {

        int[] posVector = {originalPosition.x, originalPosition.y, 1};

        return Position.at(
                (matrix[0] * posVector[0] + matrix[1] * posVector[1] + matrix[2] * posVector[2]),
                (matrix[3] * posVector[0] + matrix[4] * posVector[1] + matrix[5] * posVector[2]));

    }
}